home *** CD-ROM | disk | FTP | other *** search
/ Internet Info 1994 March / Internet Info CD-ROM (Walnut Creek) (March 1994).iso / security / log_tcp_6.0alpha.shar / hosts_access.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-07-02  |  10.9 KB  |  383 lines

  1.  /*
  2.   * This module implements a simple access control language that is based on
  3.   * host (or domain) names, netgroup, internet addresses (or network numbers)
  4.   * and daemon process names. When a match is found an optional shell command
  5.   * is executed and the search is terminated.
  6.   * 
  7.   * Diagnostics are reported through syslog(3).
  8.   * 
  9.   * Compile with -DNETGROUP if your library provides support for netgroups.
  10.   * 
  11.   * Compile with -DUSER_AT_HOST for rule-driven username lookups.
  12.   * 
  13.   * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
  14.   */
  15.  
  16. #ifndef lint
  17. static char sccsid[] = "@(#) hosts_access.c 1.10 93/03/07 22:47:36";
  18. #endif
  19.  
  20.  /* System libraries. */
  21.  
  22. #include <sys/types.h>
  23. #include <sys/param.h>
  24. #include <netinet/in.h>
  25. #include <arpa/inet.h>
  26. #include <stdio.h>
  27. #include <syslog.h>
  28. #include <ctype.h>
  29. #include <errno.h>
  30.  
  31. extern char *fgets();
  32. extern char *strchr();
  33. extern char *strtok();
  34.  
  35. #ifndef    INADDR_NONE
  36. #define    INADDR_NONE    (-1)        /* XXX should be 0xffffffff */
  37. #endif
  38.  
  39. /* Local stuff. */
  40.  
  41. #include "log_tcp.h"
  42.  
  43. #ifdef INET_ADDR_BUG
  44. #include "inet_addr_fix"
  45. #endif
  46.  
  47. /* Delimiters for lists of daemons or clients. */
  48.  
  49. static char sep[] = ", \t";
  50.  
  51. /* Constants to be used in assignments only, not in comparisons... */
  52.  
  53. #define    YES        1
  54. #define    NO        0
  55. #define    FAIL        (-1)
  56.  
  57. /* Forward declarations. */
  58.  
  59. static int table_match();
  60. static int list_match();
  61. static int client_match();
  62. static int string_match();
  63. static int masked_match();
  64. static char *xgets();
  65.  
  66. /* The user@host access control. Trivial to add but complicates use. */
  67.  
  68. #ifdef USER_AT_HOST
  69. static int userhost_match();
  70. #define CLIENT_MATCH userhost_match
  71. #else
  72. #define CLIENT_MATCH client_match
  73. #endif
  74.  
  75. /* Size of logical line buffer. */
  76.  
  77. #define    BUFLEN 2048
  78.  
  79. /* hosts_access - host access control facility */
  80.  
  81. int     hosts_access(daemon, client)
  82. char   *daemon;
  83. struct from_host *client;        /* host or user name may be empty */
  84. {
  85.  
  86.     /*
  87.      * If the (daemon, client) pair is matched by an entry in the file
  88.      * /etc/hosts.allow, access is granted. Otherwise, if the (daemon,
  89.      * client) pair is matched by an entry in the file /etc/hosts.deny,
  90.      * access is denied. Otherwise, access is granted. A non-existent
  91.      * access-control file is treated as an empty file.
  92.      */
  93.  
  94.     if (table_match(HOSTS_ALLOW, daemon, client))
  95.     return (YES);
  96.     if (table_match(HOSTS_DENY, daemon, client))
  97.     return (NO);
  98.     return (YES);
  99. }
  100.  
  101. /* table_match - match table entries with (daemon, client) pair */
  102.  
  103. static int table_match(table, daemon, client)
  104. char   *table;
  105. char   *daemon;
  106. struct from_host *client;        /* host or user name may be empty */
  107. {
  108.     FILE   *fp;
  109.     char    sv_list[BUFLEN];        /* becomes list of daemons */
  110.     char   *cl_list;            /* becomes list of clients */
  111.     char   *sh_cmd;            /* becomes optional shell command */
  112.     int     match;
  113.     int     end;
  114.  
  115.     /* The following variables should always be tested together. */
  116.  
  117.     int     sv_match = NO;        /* daemon matched */
  118.     int     cl_match = NO;        /* client matced */
  119.  
  120.     /*
  121.      * Process the table one logical line at a time. Lines that begin with a
  122.      * '#' character are ignored. Non-comment lines are broken at the ':'
  123.      * character (we complain if there is none). The first field is matched
  124.      * against the daemon process name (argv[0]), the second field against
  125.      * the host name or address. A non-existing table is treated as if it
  126.      * were an empty table. The search terminates at the first matching rule.
  127.      * When a match is found an optional shell command is executed.
  128.      */
  129.  
  130.     if (fp = fopen(table, "r")) {
  131.     while (!(sv_match && cl_match) && xgets(sv_list, sizeof(sv_list), fp)) {
  132.         if (sv_list[end = strlen(sv_list) - 1] != '\n') {
  133.         syslog(LOG_ERR, "%s: missing newline or line too long", table);
  134.         continue;
  135.         }
  136.         if (sv_list[0] == '#')        /* skip comments */
  137.         continue;
  138.         while (end > 0 && isspace(sv_list[end - 1]))
  139.          end--;
  140.         sv_list[end] = '\0';        /* strip trailing whitespace */
  141.         if (sv_list[0] == 0)        /* skip blank lines */
  142.         continue;
  143.         if ((cl_list = strchr(sv_list, ':')) == 0) {
  144.         syslog(LOG_ERR, "%s: malformed entry: \"%s\"", table, sv_list);
  145.         continue;
  146.         }
  147.         *cl_list++ = '\0';            /* split 1st and 2nd fields */
  148.         if ((sh_cmd = strchr(cl_list, ':')) != 0)
  149.         *sh_cmd++ = '\0';        /* split 2nd and 3rd fields */
  150.         if ((sv_match = list_match(sv_list, daemon, string_match)))
  151.         cl_match = list_match(cl_list, (char *) client, CLIENT_MATCH);
  152.     }
  153.     (void) fclose(fp);
  154.     } else if (errno != ENOENT) {
  155.     syslog(LOG_ERR, "cannot open %s: %m", table);
  156.     }
  157.     match = (sv_match == YES && cl_match == YES);
  158.     if (match && sh_cmd)
  159.     OPTIONS_STYLE(sh_cmd, daemon, client);
  160.     return (match);
  161. }
  162.  
  163. /* list_match - match an item against a list of tokens with exceptions */
  164.  
  165. static int list_match(list, item, match_fn)
  166. char   *list;
  167. char   *item;
  168. int   (*match_fn) ();
  169. {
  170.     char   *tok;
  171.     int     match = NO;
  172.  
  173.     /*
  174.      * Process tokens one at a time. We have exhausted all possible matches
  175.      * when we reach an "EXCEPT" token or the end of the list. If we do find
  176.      * a match, look for an "EXCEPT" list and recurse to determine whether
  177.      * the match is affected by any exceptions.
  178.      */
  179.  
  180.     for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) {
  181.     if (strcasecmp(tok, "EXCEPT") == 0)    /* EXCEPT: give up */
  182.         break;
  183.     if (match = (*match_fn) (tok, item))    /* YES or FAIL */
  184.         break;
  185.     }
  186.     /* Process exceptions to YES or FAIL matches. */
  187.  
  188.     if (match != NO) {
  189.     while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT"))
  190.          /* VOID */ ;
  191.     if (tok == 0 || list_match((char *) 0, item, match_fn) == NO)
  192.         return (match);
  193.     }
  194.     return (NO);
  195. }
  196.  
  197. /* client_match - match host name and address against token */
  198.  
  199. static int client_match(tok, item)
  200. char   *tok;
  201. char   *item;
  202. {
  203.     struct from_host *client = (struct from_host *) item;
  204.     int     match;
  205.  
  206.     /*
  207.      * Try to match the address first. If that fails, try to match the host
  208.      * name if available.
  209.      */
  210.  
  211.     if ((match = string_match(tok, client->addr)) == 0)
  212.     if (client->name[0] != 0)
  213.         match = string_match(tok, client->name);
  214.     return (match);
  215. }
  216.  
  217. #ifdef USER_AT_HOST
  218.  
  219. /* userhost_match - do user@host access control */
  220.  
  221. static int userhost_match(tok, item)
  222. char   *tok;
  223. char   *item;
  224. {
  225.     struct from_host *client = (struct from_host *) item;
  226.     int     match = NO;
  227.     char   *at;
  228.     int     host_match;
  229.     int     user_match;
  230.  
  231.     /*
  232.      * Warning: experimental code, enabled only when USER_AT_HOST is defined.
  233.      * 
  234.      * Basically, you specify user_pattern@host_pattern where remote username
  235.      * lookups are desired, and plain host_pattern for all other cases. The
  236.      * syntax of user name patterns is the same as for hosts or daemons, but
  237.      * ALL is probably the only user_pattern that makes sense.
  238.      * 
  239.      * In case of UDP connections, the result of username lookup will always be
  240.      * "unknown".
  241.      * 
  242.      * Return FAIL if we match a pattern of the form user@FAIL or FAIL@host:
  243.      * FAIL, like NO, is transitive. According to some people, such patterns
  244.      * should be taken out and shot. Good news: FAIL is on its way out.
  245.      */
  246.  
  247.     if (at = strchr(tok + 1, '@')) {        /* user@host */
  248.     *at = 0;
  249.     if (host_match = client_match(at + 1, item)) {
  250.         if (client->user[0] == 0) {
  251.         if (client->sock_type != FROM_CONNECTED) {
  252.             client->user = FROM_UNKNOWN;
  253.         } else if (client->sin == 0) {
  254.             syslog(LOG_ERR, "no socket info for user name lookup");
  255.             client->user = FROM_UNKNOWN;
  256.         } else {
  257.             client->user = rfc931_name(client->sin);
  258.         }
  259.         }
  260.         user_match = string_match(tok, client->user);
  261.         if (user_match == NO || user_match == FAIL) {
  262.         match = user_match;
  263.         } else {
  264.         match = host_match;
  265.         }
  266.     }
  267.     *at = '@';
  268.     } else {                    /* host */
  269.     match = client_match(tok, item);
  270.     }
  271.     return (match);
  272. }
  273.  
  274. #endif /* USER_AT_HOST */
  275.  
  276. /* string_match - match string against token */
  277.  
  278. static int string_match(tok, string)
  279. char   *tok;
  280. char   *string;
  281. {
  282.     int     tok_len;
  283.     int     str_len;
  284.     char   *cut;
  285. #ifdef    NETGROUP
  286.     static char *mydomain = 0;
  287. #endif
  288.  
  289.     /*
  290.      * Return YES if a token has the magic value "ALL". Return FAIL if the
  291.      * token is "FAIL". If the token starts with a "." (domain name), return
  292.      * YES if it matches the last fields of the string. If the token has the
  293.      * magic value "LOCAL", return YES if the string does not contain a "."
  294.      * character. If the token ends on a "." (network number), return YES if
  295.      * it matches the first fields of the string. If the token begins with a
  296.      * "@" (netgroup name), return YES if the string is a (host) member of
  297.      * the netgroup. Return YES if the token fully matches the string. If the
  298.      * token is a netnumber/netmask pair, return YES if the address is a
  299.      * member of the specified subnet.
  300.      */
  301.  
  302.     if (tok[0] == '.') {            /* domain: match last fields */
  303.     if ((str_len = strlen(string)) > (tok_len = strlen(tok))
  304.         && strcasecmp(tok, string + str_len - tok_len) == 0)
  305.         return (YES);
  306.     } else if (tok[0] == '@') {            /* netgroup: look it up */
  307. #ifdef    NETGROUP
  308.     if (mydomain == 0)
  309.         yp_get_default_domain(&mydomain);
  310.     if (!isdigit(string[0])
  311.         && innetgr(tok + 1, string, (char *) 0, mydomain))
  312.         return (YES);
  313. #else
  314.     syslog(LOG_ERR, "wrapper: netgroup support is not configured");
  315.     return (NO);
  316. #endif
  317.     } else if (strcasecmp(tok, "ALL") == 0) {    /* all: match any */
  318.     return (YES);
  319.     } else if (strcasecmp(tok, "FAIL") == 0) {    /* fail: match any */
  320.     return (FAIL);
  321.     } else if (strcasecmp(tok, "LOCAL") == 0) {    /* local: no dots */
  322.     if (strchr(string, '.') == 0 && strcasecmp(string, "unknown") != 0)
  323.         return (YES);
  324.     } else if (!strcasecmp(tok, string)) {    /* match host name or address */
  325.     return (YES);
  326.     } else if (tok[(tok_len = strlen(tok)) - 1] == '.') {    /* network */
  327.     if (strncmp(tok, string, tok_len) == 0)
  328.         return (YES);
  329.     } else if ((cut = strchr(tok, '/')) != 0) {    /* netnumber/netmask */
  330.     if (isdigit(string[0]) && masked_match(tok, cut, string))
  331.         return (YES);
  332.     }
  333.     return (NO);
  334. }
  335.  
  336. /* masked_match - match address against netnumber/netmask */
  337.  
  338. static int masked_match(tok, slash, string)
  339. char   *tok;
  340. char   *slash;
  341. char   *string;
  342. {
  343.     unsigned long net;
  344.     unsigned long mask;
  345.     unsigned long addr;
  346.  
  347.     if ((addr = inet_addr(string)) == INADDR_NONE)
  348.     return (NO);
  349.     *slash = 0;
  350.     net = inet_addr(tok);
  351.     *slash = '/';
  352.     if (net == INADDR_NONE || (mask = inet_addr(slash + 1)) == INADDR_NONE) {
  353.     syslog(LOG_ERR, "bad net/mask access control: %s", tok);
  354.     return (NO);
  355.     }
  356.     return ((addr & mask) == net);
  357. }
  358.  
  359. /* xgets - fgets() with backslash-newline stripping */
  360.  
  361. static char *xgets(buf, len, fp)
  362. char   *buf;
  363. int     len;
  364. FILE   *fp;
  365. {
  366.     int     got;
  367.     char   *start = buf;
  368.  
  369.     for (;;) {
  370.     if (fgets(buf, len, fp) == 0)
  371.         return (buf > start ? start : 0);
  372.     got = strlen(buf);
  373.     if (got >= 2 && buf[got - 2] == '\\' && buf[got - 1] == '\n') {
  374.         got -= 2;
  375.         buf += got;
  376.         len -= got;
  377.         buf[0] = 0;
  378.     } else {
  379.         return (start);
  380.     }
  381.     }
  382. }
  383.